{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 第二周作业——基础作业"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "1. 画图解释图像卷积滤波的基本原理，并进一步简述常见的图像平滑滤波算法。 "
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "答：卷积滤波又叫线性滤波可以说是图像处理最基本的方法，它可以允许我们对图像进行处理，产生很多不同的效果。做法很简单。首先，我们有一个二维的滤波器矩阵（有个高大上的名字叫卷积核）和一个要处理的二维图像。然后，对于图像的每一个像素点，计算它的邻域像素和滤波器矩阵的对应元素的乘积，然后加起来，作为该像素位置的值。这样就完成了滤波过程。\n",
    "常见的图像平滑滤波算法有：1）平均滤波，去一块区域内像素的平均值；2）加权平均滤波，在一个小区域内（通常3*3）像素值加权平均；3）中值滤波，确定窗口及位置(含有奇数个像素)、窗口内像素按灰度大小排序、取中间值代替原窗口中心像素值。"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "2. 简述边缘检测的基本原理，以及Sobel、LoG和Canny算子的原理差异。 "
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "答：边缘检测的基本原理就是微分或者求导，通过2次求导找到图像的边缘灰度值变化位置。有许多方法用于边缘检测，它们的绝大部分可以划分为两类：基于查找一类和基于零穿越的一类。基于查找的方法通过寻找图像一阶导数中的最大和最小值来检测边界，通常是将边界定位在梯度最大的方向。基于零穿越的方法通过寻找图像二阶导数零穿越来寻找边界，通常是Laplacian过零点或者非线性差分表示的过零点。\n",
    "Sobel算子是一种一阶微分算子，它利用像素邻近区域的梯度值来计算1个像素的梯度，然后根据一定的绝对值来取舍。\n",
    "LOG算子是先进行高斯滤波处理，然后将处理后的图像进行laplace处理。\n",
    "Canny算子是是一阶算子。其方法的实质是用1个准高斯函数作平滑运算fs=f(x,y)*G(x,y),然后以带方向的一阶微分算子定位导数最大值。\n",
    "几种算子的比较：\n",
    "Robert算子定位比较精确，但由于不包括平滑，所以对于噪声比较敏感。Prewitt算子和Sobel算子都是一阶的微分算子，而前者是平均滤波，后者是加权平均滤波且检测的图像边缘可能大于2个像素。这两者对灰度渐变低噪声的图像有较好的检测效果，但是对于混合多复杂噪声的图像，处理效果就不理想了。LOG滤波器方法通过检测二阶导数过零点来判断边缘点。LOG滤波器中的a正比于低通滤波器的宽度，a越大，平滑作用越显著，去除噪声越好，但图像的细节也损失越大，边缘精度也就越低。所以在边缘定位精度和消除噪声级间存在着矛盾，应该根据具体问题对噪声水平和边缘点定位精度要求适当选取。\n",
    "讨论和比较了几种常用的边缘检测算子。梯度算子计算简单,但精度不高,只能检测出图像大致的轮廓,而对于比较细的边缘可能会忽略。Prewitt 和Sobel 算子比Roberts 效果要好一些。LOG 滤波器和Canny 算子的检测效果优于梯度算子,能够检测出图像较细的边缘部分。不同的系统,针对不同的环境条件和要求,选择合适的算子来对图像进行边缘检测。"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "3. 简述图像直方图的基本概念，及使用大津算法进行图像分割的基本原理。 "
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "答：图像直方图就是图中某灰度值在图像中的数量或出现的概率的分布图。图像灰度值都集中在较暗区域，表示当前图像较暗；当前图案灰度值较为集中，表示对比度低；图像灰度值都集中在较亮区域，表示当前图像较亮；当前灰度值比较平均，表示对比度较高。\n",
    "大津算法：确定最佳阈值，使背景和目标之间的类间方差最大。每个波峰对应的部分称为一类，每一类自己之间灰度值差异较小，不同类之间灰度值差异较大。但是大津算法有缺点：与目标图像接近的噪声点会存在；对于渐变的图案，没有一个明显的边界，因此大津算法的阈值分割会从中间点切开，造成图像分离。"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "4. 简述Harris算子对角点的定义，进行角点检测的基本原理，并说明引入角点响应函数的意义。 "
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "答：在灰度变化平缓区域，窗口内像素灰度积分近似保持不变；在边缘区域，边缘方向：灰度积分近似不变，其余任意方向：剧烈变化；在角点处，任意方向均剧烈变化。\n",
    "通过灰度积分变化，得到有关λ1、λ2参数的函数，当λ1、λ2都比较小时，点(x, y)处于灰度变化平缓区域；当λ1远大于λ2或者λ1远小于λ2时，点(x, y)为边界像素；当λ1、λ2都比较大，且近似相等时，点(x, y)为角点。\n",
    "但是这里λ1、λ2怎么算大怎么算小呢，不容易衡量，因此引入了角点响应函数R：当R接近于零时，处于灰度变化平缓区域；当R<0时，点为边界像素；当R>0时，点为角点。"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "5. 简述Hough变换的基本原理(包括参数空间变换及参数空间划分网格统计)。 "
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "答：  Hough变换的基本原理在于，利用点与线的对偶性，将图像空间的线条变为参数空间的聚集点，从而检测给定图像是否存在给定性质的曲线。\n",
    "也就是将原有的空间转换成极坐标系显示，这样一条直线上的所有点在极坐标系下就会汇聚于一个点。\n",
    "核心思想：直线y=kx+b每一条直线对应一个k, b,极坐标下对应一个点(ρ,θ)；直角坐标系的一点(x,y)，对应极坐标系下的一条正弦曲线ρ=xcosθ+ysinθ，同一条直线上的多个点，在极坐标系下必相交于一点；通过边缘检测，可以得到每个边缘的一系列点，然后将各个点带入ρθ方程，得到每个xy点的余弦曲线，这些曲线相交的点一般会集中在一定区域内（小格内），每条线的长度就是这个小格内的点数，对应的ρθ就是直线参数。"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "6. 简述SIFT原理(重点是尺度空间和方向直方图原理)及ORB算子原理(重点是FAST和BRIEF)。 "
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "答：1）SIFT原理：\n",
    "尺度不变特征转换(Scale-invariant feature transform或SIFT)是一种电脑视觉的算法用来侦测与描述影像中的局部性特征，它在空间尺度中寻找极值点，并提取出其位置、尺度、旋转不变量，此算法由 David Lowe在1999年所发表，2004年完善总结。\n",
    "其应用范围包含物体辨识、机器人地图感知与导航、影像缝合、3D模型建立、手势辨识、影像追踪和动作比对。\n",
    "此算法有其专利，专利拥有者为英属哥伦比亚大学。\n",
    "局部影像特征的描述与侦测可以帮助辨识物体，SIFT 特征是基于物体上的一些局部外观的兴趣点而与影像的大小和旋转无关。对于光线、噪声、些微视角改变的容忍度也相当高。基于这些特性，它们是高度显著而且相对容易撷取，在母数庞大的特征数据库中，很容易辨识物体而且鲜有误认。使用 SIFT特征描述对于部分物体遮蔽的侦测率也相当高，甚至只需要3个以上的SIFT物体特征就足以计算出位置与方位。在现今的电脑硬件速度下和小型的特征数据库条件下，辨识速度可接近即时运算。SIFT特征的信息量大，适合在海量数据库中快速准确匹配。\n",
    "SIFT算法的特点有：\n",
    "1. SIFT特征是图像的局部特征，其对旋转、尺度缩放、亮度变化保持不变性，对视角变化、仿射变换、噪声也保持一定程度的稳定性；\n",
    "2. 独特性（Distinctiveness）好，信息量丰富，适用于在海量特征数据库中进行快速、准确的匹配；\n",
    "3. 多量性，即使少数的几个物体也可以产生大量的SIFT特征向量；\n",
    "4. 高速性，经优化的SIFT匹配算法甚至可以达到实时的要求；\n",
    "5. 可扩展性，可以很方便的与其他形式的特征向量进行联合。\n",
    "SIFT算法可以解决的问题：\n",
    "目标的自身状态、场景所处的环境和成像器材的成像特性等因素影响图像配准/目标识别跟踪的性能。而SIFT算法在一定程度上可解决：\n",
    "1. 目标的旋转、缩放、平移（RST）\n",
    "2. 图像仿射/投影变换（视点viewpoint）\n",
    "3. 光照影响（illumination）\n",
    "4. 目标遮挡（occlusion）\n",
    "5. 杂物场景（clutter）\n",
    "6. 噪声\n",
    "SIFT算法的实质是在不同的尺度空间上查找关键点(特征点)，并计算出关键点的方向。SIFT所查找到的关键点是一些十分突出，不会因光照，仿射变换和噪音等因素而变化的点，如角点、边缘点、暗区的亮点及亮区的暗点等。 \n",
    "Lowe将SIFT算法分解为如下四步：\n",
    "1. 尺度空间极值检测：搜索所有尺度上的图像位置。通过高斯微分函数来识别潜在的对于尺度和旋转不变的兴趣点。\n",
    "2. 关键点定位：在每个候选的位置上，通过一个拟合精细的模型来确定位置和尺度。关键点的选择依据于它们的稳定程度。\n",
    "3. 方向确定：基于图像局部的梯度方向，分配给每个关键点位置一个或多个方向。所有后面的对图像数据的操作都相对于关键点的方向、尺度和位置进行变换，从而提供对于这些变换的不变性。\n",
    "4. 关键点描述：在每个关键点周围的邻域内，在选定的尺度上测量图像局部的梯度。这些梯度被变换成一种表示，这种表示允许比较大的局部形状的变形和光照变化。\n",
    "\n",
    "尺度空间理论的基本思想是：在图像信息处理模型中引入一个被视为尺度的参数，通过连续变化尺度参数获得多尺度下的尺度空间表示序列，对这些序列进行尺度空间主轮廓的提取，并以该主轮廓作为一种特征向量，实现边缘、角点检测和不同分辨率上的特征提取等。\n",
    "尺度空间方法将传统的单尺度图像信息处理技术纳入尺度不断变化的动态分析框架中，更容易获取图像的本质特征。尺度空间中各尺度图像的模糊程度逐渐变大，能够模拟人在距离目标由近到远时目标在视网膜上的形成过程。\n",
    "\n",
    "为了使描述符具有旋转不变性，需要利用图像的局部特征为给每一个关键点分配一个基准方向。使用图像梯度的方法求取局部结构的稳定方向。对于在DOG金字塔中检测出的关键点点，采集其所在高斯金字塔图像3σ邻域窗口内像素的梯度和方向分布特征。在完成关键点的梯度计算后，使用直方图统计邻域内像素的梯度和方向。梯度直方图将0~360度的方向范围分为36个柱(bins)，其中每柱10度。方向直方图的峰值则代表了该特征点处邻域梯度的方向，以直方图中最大值作为该关键点的主方向。为了增强匹配的鲁棒性，只保留峰值大于主方向峰值80％的方向作为该关键点的辅方向。因此，对于同一梯度值的多个峰值的关键点位置，在相同位置和尺度将会有多个关键点被创建但方向不同。仅有15％的关键点被赋予多个方向，但可以明显的提高关键点匹配的稳定性。实际编程实现中，就是把该关键点复制成多份关键点，并将方向值分别赋给这些复制后的关键点，并且，离散的梯度方向直方图要进行插值拟合处理，来求得更精确的方向角度值。"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "答：2）ORB算子原理：\n",
    "ORB利用FAST找到图像特征点，利用BRIEF对特征点进行描述。\n",
    "FAST具体计算过程：\n",
    "从图像中选取一点P，以P为圆心画一个半径为3像素的圆。圆周上如果有连续N个像素点的灰度值比P点的灰度值大戒小，则认为P为特征点。\n",
    "快速算法：为了加快特征点的提取，快速排出非特征点，首先检测1、5、9、13位置上的灰度值，如果P是特征点，那么这四个位置上有3个戒3个以上的的像素值都大于戒者小于P点的灰度值。如果丌满足，则直接排出此点。\n",
    "BRIEF具体计算过程：\n",
    "BRIEF算法计算出来的是一个二进制串的特征描述符。它是在一个特征点的邻域内，选择n对像素点pi、qi（i=1,2,…,n）。\n",
    "比较每个点对的灰度值的大小，如果I(pi)> I(qi)，则生成二进制串中的1，否则为0。\n",
    "所有的点对都进行比较，则生成长度为n的二进制串。一般n取128、256或512，opencv默认为256。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 第二周作业——进阶作业"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "1. 以Lena为原始图像，通过OpenCV实现平均滤波，高斯滤波及中值滤波，比较滤波结果。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#各种滤波算法\n",
    "import cv2\n",
    "import numpy as np\n",
    "\n",
    "img = cv2.imread(r'.\\lena.jpg')\n",
    "\n",
    "#下面都以3乘3滤波操作为比对例子：\n",
    "#均值滤波\n",
    "imgMean = cv2.blur(img,(5,5))\n",
    "#高斯滤波\n",
    "imgGauss = cv2.GaussianBlur(img,(5,5),0)\n",
    "#中值滤波\n",
    "imgMed = cv2.medianBlur(img,5)\n",
    "#---------形态学滤波-先开后闭滤波--------\n",
    "#首先定义结构元素\n",
    "'''\n",
    "形态学处理的核心就是定义结构元素，\n",
    "在OpenCV-Python中，可以使用其自带的getStructuringElement函数，\n",
    "也可以直接使用NumPy的ndarray来定义一个结构元素。\n",
    "'''\n",
    "#生成十字形的结构矩阵，这里生成十字型矩阵是举个例子，还有其他类型的矩阵可以根据实际需要变化\n",
    "MP_kernel = cv2.getStructuringElement(cv2.MORPH_CROSS,(5,5))\n",
    "\n",
    "NpKernel = np.uint8(np.zeros((5,5)))\n",
    "for i in range(5):\n",
    "\tNpKernel[2, i] = 1\n",
    "\tNpKernel[i, 2] = 1\n",
    "\n",
    "'''\n",
    "print(\"elemrnt\":MP_kernel )\n",
    "print(\"NpKernel \",NpKernel )\n",
    "上述结果输出（相同）：\n",
    "array([[0, 0, 1, 0, 0],\n",
    "       [0, 0, 1, 0, 0],\n",
    "       [1, 1, 1, 1, 1],\n",
    "       [0, 0, 1, 0, 0],\n",
    "       [0, 0, 1, 0, 0]], dtype=uint8)\n",
    "十字形的结构矩阵，用于形态滤波的结构输入\n",
    "'''\n",
    "#开滤波,iteration表示迭代次数\n",
    "imgOpen = cv2.morphologyEx(img,cv2.MORPH_OPEN,MP_kernel,iterations=3)\n",
    "#闭滤波\n",
    "imgClose = cv2.morphologyEx(imgOpen,cv2.MORPH_CLOSE,MP_kernel,iterations=3)\n",
    "\n",
    "#-------形态学滤波结束-------------------\n",
    "cv2.imshow('img',img)\n",
    "cv2.imshow('imgMean',imgMean)\n",
    "cv2.imshow('imgGauss',imgGauss)\n",
    "cv2.imshow('imgMed',imgMed)\n",
    "cv2.imshow('imgOpen',imgOpen)\n",
    "cv2.imshow('imgClose',imgClose)\n",
    "\n",
    "cv2.waitKey()\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "2. 以Lena为原始图像，通过OpenCV使用Sobel及Canny算子检测，比较边缘检测结果。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 实现对图像进行边缘检测\n",
    "import cv2\n",
    "import  numpy as np\n",
    "\n",
    "img = cv2.imread(r'.\\lena.jpg')\n",
    "imgGray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)\n",
    "\n",
    "# 这里建议在边缘检测前都对图像进行灰度转换，sobel算子不对图像格式有要求，canny算子强制要求图像必须是单通道灰度图\n",
    "\n",
    "# Robert 算子边缘检测---------------------------开始------------\n",
    "'''\n",
    " Roberts边缘算子是一个2x2的模板，采用的是对角方向相邻的两个像素之差,对角线差分\n",
    " 从图像处理的实际效果来看，边缘定位较准，对噪声敏感。\n",
    " 适用于边缘明显且噪声较少的图像分割。\n",
    " Roberts边缘检测算子是一种利用局部差分算子寻找边缘的算子,\n",
    " Robert算子图像处理后结果边缘不是很平滑。\n",
    " 经分析，由于Robert算子通常会在图像边缘附近的区域内产生较宽的响应，\n",
    " 故采用上述算子检测的边缘图像常需做细化处理，边缘定位的精度不是很高。\n",
    "\n",
    "'''\n",
    "def Robert_proc(imgSrc):\n",
    "    '''\n",
    "    本函数实现的Robert计算方法只能计算单通道的灰度图，而且对噪声非常敏感\n",
    "    '''\n",
    "    X,Y = imgSrc.shape\n",
    "    NpImg = np.zeros(imgSrc.shape[:2], dtype=\"float64\");  # 创建与原始图像相同大小的零矩阵\n",
    "    #print(NpImg)\n",
    "    for x in range(X):\n",
    "        for y in range(Y):\n",
    "            if (x<X-1 and y<Y-1):\n",
    "                xtemp = float(imgSrc[x+1][y+1])-float(imgSrc[x][y])\n",
    "                ytemp = float(imgSrc[x][y+1])-float(imgSrc[x+1][y])\n",
    "                #print(pow((xtemp * xtemp + ytemp * ytemp), 1 / 2))\n",
    "                NpImg[x,y] = pow((xtemp*xtemp+ytemp*ytemp),1/2)\n",
    "    return NpImg\n",
    "\n",
    "imgRobert = Robert_proc(imgGray)\n",
    "#cv2.imshow('Robert',imgRobert)\n",
    "# Robert算子边缘检测--------------------------结束------------\n",
    "\n",
    "# sobel算子边缘检测------------------------------开始-----------\n",
    "# ### 图像边缘处理sobel细节\n",
    "sobelx = cv2.Sobel(imgGray,cv2.CV_64F, 1, 0, ksize=3)\n",
    "# 利用Sobel方法可以进行sobel边缘检测\n",
    "# img表示源图像，即进行边缘检测的图像\n",
    "# cv2.CV_64F表示64位浮点数即64float，这里如果填-1，表示输出与原图保持一致\n",
    "# 这里不使用numpy.float64，因为可能会发生溢出现象。用cv的数据则会自动\n",
    "# 第三和第四个参数分别是对X和Y方向的导数（即dx,dy），对于图像来说就是差分，这里1表示对X求偏导（差分），0表示不对Y求导（差分）。其中，X还可以求2次导。\n",
    "# 注意：对X求导就是检测X方向上是否有边缘。\n",
    "# 第五个参数ksize是指核的大小。\n",
    "\n",
    "# 这里说明一下，这个参数的前四个参数都没有给谁赋值，而ksize则是被赋值的对象\n",
    "# 实际上，这时可省略的参数，而前四个是不可省的参数。注意其中的不同点\n",
    "\n",
    "sobely = cv2.Sobel(imgGray, cv2.CV_64F, 0, 1, ksize=3)\n",
    "# 与上面不同的是对y方向进行边缘检测\n",
    "\n",
    "sobelXY_64F = cv2.Sobel(imgGray, cv2.CV_64F, 1, 1, ksize=3)  #64位浮点型输出\n",
    "# sobelXY_8U = cv2.Sobel(imgGray, -1, 1, 1, ksize=3)\n",
    "# 与原图保持一致的8位无符号整型输出，1阶导，直接这样输出存在bug，如下描述\n",
    "# 这里对两个方向同时进行检测，则会过滤掉仅仅只是x或者y方向上的边缘\n",
    "'''\n",
    "使用sobel算子时有个问题，对于我们常用的RGB格式图片，\n",
    "存储一种颜色用的是8bit存储，所以在做从高到低的差分时，\n",
    "比如白色到黑色，算出来的差分是负数，\n",
    "而对于RGB通道的图片，负数溢出没有意义，所以会出现从高到低差分bug.\n",
    "解决办法如下：将sobel算子的数据格式，换到更大的，比如cv2.CV_64F，\n",
    "再计算 带负数的结果 的绝对值，再将这个绝对值转回到8bit的格式。\n",
    "如下所示：\n",
    "img =cv2.imread(\"edge.png\",0)\n",
    "sobelx=cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5)\n",
    "sobelx=np.absolute(sobelx)\n",
    "sobelx=np.uint8(sobelx)\n",
    "'''\n",
    "sobelXY_temp = np.absolute(sobelXY_64F)\n",
    "sobelXY_8U = np.uint8(sobelXY_temp)\n",
    "# sobel算子边缘检测------------------------------结束-----------\n",
    "\n",
    "# laplace算子边缘检测---------------------------开始------------\n",
    "imgLaplace = cv2.Laplacian(imgGray,-1)  # 使用laplace算子计算，输出图像格式与原图一致\n",
    "\n",
    "# laplace算子边缘检测---------------------------结束------------\n",
    "\n",
    "# Log算子边缘检测--------------------------------开始-----------\n",
    "#首先将原始图像经过高斯滤波变换\n",
    "imgGauss = cv2.GaussianBlur(img,(3,3),0)\n",
    "#然后将变换过的图转换成灰度图\n",
    "imgGaussGray = cv2.cvtColor(imgGauss,cv2.COLOR_BGR2GRAY)\n",
    "#然后对该灰度图进行laplace边缘检测计算\n",
    "imgLog = cv2.Laplacian(imgGaussGray,-1)\n",
    "#cv2.imshow('imgGaussGray',imgGaussGray)\n",
    "#cv2.imshow('imgLog',imgLog)\n",
    "# Log算子边缘检测--------------------------------结束-----------\n",
    "\n",
    "\n",
    "# canny算子边缘检测------------------------------开始-----------\n",
    "imgCanny = cv2.Canny(imgGray,50,150)\n",
    "\"\"\"\n",
    "cv2.Canny(image,            # 输入原图（必须为单通道图）\n",
    "          threshold1,       #阈值1\n",
    "          threshold2,       # 较大的阈值2用于检测图像中明显的边缘\n",
    "          [, edges[, \n",
    "          apertureSize[,    # apertureSize：Sobel算子的大小\n",
    "          L2gradient ]]])   # 参数(布尔值)：\n",
    "                              true： 使用更精确的L2范数进行计算（即两个方向的倒数的平方和再开放），\n",
    "                              false：使用L1范数（直接将两个方向导数的绝对值相加）。\n",
    "\"\"\"\n",
    "\n",
    "# canny算子边缘检测------------------------------结束-----------\n",
    "\n",
    "# 对比一下这几种边缘检测结果\n",
    "# cv2.imshow('img',img)\n",
    "# cv2.imshow('imgGray',imgGray)\n",
    "# cv2.imshow('sobelx',sobelx)\n",
    "# cv2.imshow('sobely',sobely)\n",
    "\n",
    "cv2.imshow('sobelXY_64F',sobelXY_64F)\n",
    "cv2.imshow('sobelXY_8U',sobelXY_8U)\n",
    "cv2.imshow('Robert',imgRobert)\n",
    "cv2.imshow('imgLaplace',imgLaplace)\n",
    "cv2.imshow('imgLog',imgLog)\n",
    "cv2.imshow('imgCanny',imgCanny)\n",
    "\n",
    "cv2.waitKey()\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "3. 在OpenCV安装目录下找到课程对应演示图片(安装目录\\sources\\samples\\data)，首先计算灰度直方图，进一步使用大津算法进行分割，并比较分析分割结果。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 这里讲解图像直方图和图像分割函数\n",
    "\n",
    "import cv2\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "img_1 = cv2.imread(r'C:/XYX_use/AI_course/AI_learn/AI_homework/week2/pic2.png')\n",
    "img_2 = cv2.imread(r'C:/XYX_use/AI_course/AI_learn/AI_homework/week2/pic6.png')\n",
    "\n",
    "#现将各个图像转成单通道灰度图\n",
    "img_1_gray = cv2.cvtColor(img_1,cv2.COLOR_BGR2GRAY)\n",
    "img_2_gray = cv2.cvtColor(img_2,cv2.COLOR_BGR2GRAY)\n",
    "\n",
    "#计算灰度直方图\n",
    "'''\n",
    "Opencv给我们提供的函数是cv2.calcHist()，该函数有5个参数：\n",
    "image输入图像，传入时应该用中括号[]括起来\n",
    "channels:：传入图像的通道，如果是灰度图像，那就不用说了，只有一个通道，值为0，\n",
    "         如果是彩色图像（有3个通道），那么值为0,1,2,中选择一个，对应着BGR各个通道。这个值也得用[]传入。\n",
    "mask：掩膜图像。如果统计整幅图，那么为none。主要是如果要统计部分图的直方图，就得构造相应的炎掩膜来计算。\n",
    "histSize：灰度级的个数，需要中括号，比如[256]\n",
    "ranges:像素值的范围，通常[0,256]，有的图像如果不是0-256，比如说你来回各种变换导致像素值负值、很大，则需要调整后才可以。\n",
    "'''\n",
    "img_1_hist = cv2.calcHist([img_1_gray],[0],None,[256],[0,256])\n",
    "img_2_hist = cv2.calcHist([img_2_gray],[0],None,[256],[0,256])\n",
    "\n",
    "#大津滤波\n",
    "'''\n",
    "cv2.THRESH_BINARY（黑白二值） ,加上大津滤波的算法cv2.THRESH_OTSU\n",
    "通过函数cv2.threshold会自动找到一个介于两波峰之间的阈值。\n",
    "'''\n",
    "ret1,img_1_otsu = cv2.threshold(img_1_gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)\n",
    "ret2,img_2_otsu = cv2.threshold(img_2_gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)\n",
    "\n",
    "#漫水填充法/区域生长法\n",
    "'''\n",
    "floodFill(image, mask, seedPoint, newVal, loDiff=None, upDiff=None, flags=None)\n",
    "image:输入图像，可以是一通道或者是三通道。\n",
    "mask:该版本特有的掩膜。 单通道，8位，在长宽上都比原图像image多2个像素点。\n",
    "     另外，当flag为FLOORFILL_MAK_ONLY时，只会填充mask中数值为0的区域。\n",
    "seedPoint:漫水填充的种子点，即起始点。\n",
    "newVal:被填充的像素点新的像素值\n",
    "loDiff：表示当前的观察点像素值与其相邻区域像素值或待加入该区域的像素之间的亮度或颜色之间负差的最大值。\n",
    "upDiff:表示当前的观察点像素值与其相邻区域像素值或待加入该区域的像素之间的亮度或颜色之间负差的最小值。\n",
    "flag：\n",
    "当为CV_FLOODFILL_FIXED_RANGE 时，待处理的像素点与种子点作比较，在范围之内，则填充此像素 。（改变图像）\n",
    "CV_FLOODFILL_MASK_ONLY 此位设置填充的对像， 若设置此位，则mask不能为空，此时，函数不填充原始图像img，而是填充掩码图像.\n",
    "'''\n",
    "#泛洪填充(彩色图像填充)\n",
    "def fill_color_demo(image,x,y,R,G,B,max,min):\n",
    "    copyImg = image.copy()\n",
    "    h, w = image.shape[:2]\n",
    "    mask = np.zeros([h+2, w+2],np.uint8)   #mask必须行和列都加2，且必须为uint8单通道阵列\n",
    "    #为什么要加2可以这么理解：当从0行0列开始泛洪填充扫描时，mask多出来的2可以保证扫描的边界上的像素都会被处理\n",
    "    cv2.floodFill(copyImg, mask, (x, y), (R, G, B), (max, max, max), (min, min, min), cv2.FLOODFILL_FIXED_RANGE)\n",
    "    #cv2.imshow(\"fill_color_demo\", copyImg)\n",
    "    return copyImg\n",
    "hh,ww = img_1.shape[:2]\n",
    "xx,yy = img_2.shape[:2]\n",
    "print(xx,'\\n',yy)\n",
    "img_1_fill = fill_color_demo(img_1,int(hh/100),int(ww/100),255,255,255,200,190)\n",
    "img_1_fill2 = fill_color_demo(img_1_fill,int(hh/3),int(ww/3),0,0,255,250,250)\n",
    "img_1_fill3 = fill_color_demo(img_1_fill2,2*int(hh/3),int(ww/2),0,255,0,250,250)\n",
    "img_1_fill4 = fill_color_demo(img_1_fill3,3*int(hh/5),int(ww/5),255,0,0,50,0)\n",
    "img_2_fill = fill_color_demo(img_2,int(xx/20),int(yy/20),255,255,255,0.5,0)\n",
    "img_2_fill2 = fill_color_demo(img_2_fill,int(xx/2),int(yy/3),255,0,0,150,130)\n",
    "#img_2_fill3 = fill_color_demo(img_2_fill2,210,300,255,0,0,150,130)\n",
    "#疑问：如何确定起始点的具体值？\n",
    "\n",
    "cv2.imshow('pic2_fill',img_1_fill3)\n",
    "cv2.imshow('pic6_fill',img_2_fill2)\n",
    "\n",
    "#显示运算结果\n",
    "#cv2.imshow('img1',img_1)\n",
    "#cv2.imshow('img2',img_2)\n",
    "cv2.imshow('pic2_otsu',img_1_otsu)\n",
    "cv2.imshow('pic6_otsu',img_2_otsu)\n",
    "\n",
    "'''\n",
    "#直方图显示\n",
    "plt.figure()\n",
    "plt.title(\"pic2.png Histogram\")\n",
    "plt.xlabel(\"Bins\")\n",
    "plt.ylabel(\"# of Pixels\")\n",
    "plt.plot(img_1_hist)\n",
    "plt.xlim([0, 256])\n",
    "plt.show()\n",
    "\n",
    "plt.figure()\n",
    "plt.title(\"pic6.png Histogram\")\n",
    "plt.xlabel(\"Bins\")\n",
    "plt.ylabel(\"# of Pixels\")\n",
    "plt.plot(img_2_hist)\n",
    "plt.xlim([0, 256])\n",
    "plt.ylim([0,4000])\n",
    "plt.show()\n",
    "'''\n",
    "\n",
    "cv2.waitKey()\n",
    "cv2.destroyAllWindows()\n"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "4. 使用米粒图像，分割得到各米粒，首先计算各区域(米粒)的面积、长度等信息，进一步计算面积、长度的均值及方差，分析落在3sigma范围内米粒的数量。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#实现米粒数目的识别\n",
    "import cv2 as cv\n",
    "import copy\n",
    "\n",
    "img = cv.imread(r'C:/XYX_use/AI_course/AI_learn/AI_homework/week2/mili.jpg')\n",
    "\n",
    "#第一步：将彩色图片转成单通道灰度图\n",
    "gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)\n",
    "\n",
    "#第二步，对灰度图进行大津算法的阈值图像分割-因为这个图片光照比较均匀，黑白分明，因此采用大津算法可以\n",
    "#如果光照不均匀需采用其他算法进行图像分割，比如自适应算法等等\n",
    "ret1,gray_otsu = cv.threshold(gray,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)\n",
    "#通过上述的大津算法发现有部分米粒被漏掉了，因为从网上下载的米粒图像下面的光照不均，因此采用自适应算法\n",
    "#gray_otsu = cv.adaptiveThreshold(gray,255,cv.ADAPTIVE_THRESH_MEAN_C,cv.THRESH_BINARY,11,2)\n",
    "\n",
    "#第三步，对图像进行形态学滤波\n",
    "MP_kernel = cv.getStructuringElement(cv.MORPH_CROSS,(3,3)) #采用3*3的十字形矩阵作为结构矩阵\n",
    "gray_otsu_Open = cv.morphologyEx(gray_otsu,cv.MORPH_OPEN,MP_kernel)\n",
    "\n",
    "seg = copy.deepcopy(gray_otsu_Open)\n",
    "'''\n",
    "对deepcopy和普通的copy进行讲解\n",
    "**直接上结论： \n",
    "—–我们寻常意义的复制就是深复制，即将被复制对象完全再复制一遍作为独立的新个体单独存在。\n",
    "所以改变原有被复制对象不会对已经复制出来的新对象产生影响。 \n",
    "—–而浅复制并不会产生一个独立的对象单独存在，他只是将原有的数据块打上一个新标签，\n",
    "所以当其中一个标签被改变的时候，数据块就会发生变化，另一个标签也会随之改变。这就和我们寻常意义上的复制有所不同了。**\n",
    "'''\n",
    "#进行边缘检测——轮廓检测\n",
    "'''\n",
    "findcounters返回值有三个，第二个表示轮廓，是个列表。\n",
    "函数cv2.findContours()有三个参数。第一个是输入图像，第二个是轮廓检索模式，第三个是轮廓近似方法。\n",
    "cv2.findContours()第二个参数有什么用？\n",
    "对于轮廓来说可能还包含父子关系，也就是说一个轮廓里面可能还包含着其他轮廓\n",
    "（比如说房子的轮廓可以包含一个矩形的窗户，则房子为父，窗户为子，而窗户里可能有个人的头，这又是一种父子关系），\n",
    "这其中的层次结构可以通过对第二个参数设置来选择是否获取\n",
    "而在OpenCV有哪些参数可选呢？\n",
    "RETR_LIST 从解释的角度来看，这中应是最简单的。它只是提取所有的轮廓，而不去创建任何父子关系。\n",
    "RETR_EXTERNAL 如果你选择这种模式的话，只会返回最外边的的轮廓，所有的子轮廓都会被忽略掉。\n",
    "RETR_CCOMP 在这种模式下会返回所有的轮廓并将轮廓分为两级组织结构。\n",
    "RETR_TREE 这种模式下会返回所有轮廓，并且创建一个完整的组织结构列表。它甚至会告诉你谁是爷爷，爸爸，儿子，孙子等。\n",
    "\n",
    "findContours()第三个参数什么意思呢？\n",
    "如果设为cv2.CHAIN_APPROX_NONE，，表示边界所有点都会被储存；\n",
    "而如果设为cv2.CHAIN_APPROX_SIMPLE 会压缩轮廓，将轮廓上冗余点去掉，比如说四边形就会只储存四个角点。\n",
    "也就是说NONE形式会将所有边缘轮廓点都存储，而SIMPLE仅将轮廓的特殊点进行存储以进行压缩\n",
    "'''\n",
    "binary, contours, hierarchy = cv.findContours(seg,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_NONE)\n",
    "#print('binary={}\\n,contours={}\\n,hierarchy={}\\n'.format(binary,contours,hierarchy))\n",
    "#找到轮廓后，可以用drawContours函数把轮廓画出来\n",
    "\n",
    "#开始统计米粒的个数和面积信息\n",
    "countN = 0\n",
    "list_rice = []  #创建一个空列表，用于存储所有米粒的信息\n",
    "for i in range(len(contours),0,-1):\n",
    "    c = contours[i-1]  # 每个轮廓表示一个米粒，这里就是将每个轮廓的元素提取出来\n",
    "    area = cv.contourArea(c)  #计算每个米粒的面积\n",
    "    if area<10:   #面积小于10的米粒不要，认为是噪声，选取下一个\n",
    "        continue\n",
    "    rice_len = cv.arcLength(c,True) #计算轮廓的周长，参数1表示图像轮廓，参数2表示轮廓是否封闭，即算出每个米粒的周长\n",
    "    countN += 1\n",
    "    #print('bolb {} : area={},length={}'.format(i,area,rice_len))\n",
    "    #将每个米粒的信息填入字典中\n",
    "    rice_info = {}\n",
    "    rice_info[\"cnt\"]=countN\n",
    "    rice_info[\"area\"]=area\n",
    "    rice_info[\"length\"]=rice_len\n",
    "    print(rice_info)\n",
    "    #将每个米粒的字典信息写入全局列表中\n",
    "    list_rice.append(rice_info)\n",
    "\n",
    "    #x,y,w,h = cv.boundingRect(c)  #计算每个轮廓的包围矩阵\n",
    "    '''\n",
    "    这个函数很简单，img是一个二值图，也就是它的参数；\n",
    "    返回四个值，分别是x，y，w，h；\n",
    "    x，y是矩阵左上点的坐标，w，h是矩阵的宽和高\n",
    "    然后利用cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)画出矩行\n",
    "    参数解释\n",
    "    第一个参数：img是原图\n",
    "    第二个参数：（x，y）是矩阵的左上点坐标\n",
    "    第三个参数：（x+w，y+h）是矩阵的右下点坐标\n",
    "    第四个参数：（0,255,0）是画线对应的rgb颜色\n",
    "    第五个参数：2是所画的线的宽度\n",
    "    '''\n",
    "    x, y, w, h = cv.boundingRect(c)\n",
    "    cv.rectangle(img, (x, y), (x + w, y + h), (0, 0, 255), 1) #用红色线将轮廓画出\n",
    "    cv.putText(img,str(countN),(x,y),cv.FONT_HERSHEY_PLAIN,0.5,(0,255,0),1)\n",
    "    #putText参数解释：各参数依次是：图片，添加的文字，左上角坐标，字体，字体大小，颜色，字体粗细\n",
    "\n",
    "    '''\n",
    "    计算包围矩阵就是用一个最小的矩形，把找到的形状包起来。\n",
    "    这里boundingRect的包围矩阵是水平的，即不论被包围的图像是不是倾斜，这个矩阵都是水平的。\n",
    "    还有一个带旋转的矩形，面积会更小，就是minAreaRect，这样矩阵就能根据图形的倾斜情况自行旋转找到最小包围方式\n",
    "    '''\n",
    "    '''\n",
    "    #采用最小面积矩阵/斜矩阵进行形状包围\n",
    "    x, y, width, height, angle = cv.minAreaRect(c)    \n",
    "    #minAreaRect函数返回矩形的中心点坐标，长宽，旋转角度[-90,0)，当矩形水平或竖直时均返回-90\n",
    "    '''\n",
    "print('the number of rice is {}\\n'.format(countN))\n",
    "#print(list_rice)\n",
    "#开始计算米粒的面积和周长的均值和方差\n",
    "rice_cnt = len(list_rice)  #大米列表中的元素个数就是米粒的个数\n",
    "rice_areas = 0\n",
    "rice_lens = 0\n",
    "for j in range(0,rice_cnt):\n",
    "    rice_areas = rice_areas + list_rice[j]['area']\n",
    "    rice_lens = rice_lens + list_rice[j]['length']\n",
    "\n",
    "#求均值\n",
    "area_arv = rice_areas/rice_cnt\n",
    "lens_arv = rice_lens/rice_cnt\n",
    "print('area-arv = ',area_arv)\n",
    "print('lens-arv = ',lens_arv)\n",
    "\n",
    "#求方差\n",
    "'''\n",
    "sigma²=（∑（X-xArv）²）/cntX\n",
    "'''\n",
    "temp_area = 0\n",
    "temp_len = 0\n",
    "for k in range(0,rice_cnt):\n",
    "    temp_area = temp_area + (list_rice[k]['area']-area_arv)*(list_rice[k]['area']-area_arv)\n",
    "    temp_len = temp_len + (list_rice[k]['length'] - lens_arv) * (list_rice[k]['length'] - lens_arv)\n",
    "sigma_area = temp_area/rice_cnt\n",
    "sigma_len = temp_len/rice_cnt\n",
    "print('面积方差=',sigma_area)\n",
    "print('周长方差=',sigma_len)\n",
    "\n",
    "#分析落在3sigma范围内米粒的数量\n",
    "#这个什么意思没看懂？？？\n",
    "\n",
    "cv.imshow('init_img',img)\n",
    "cv.imshow('threshold_img',gray_otsu_Open)\n",
    "\n",
    "cv.waitKey()\n",
    "cv.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "这里面要去“分析落在3sigma范围内米粒的数量？？？”是什么意思？能讲解一下吗？？是均方差在3以内的米粒数吗？但是统计哪些米粒的均值才能算均方差的标准比较点呢？？这里不明白？"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 扩展作业"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "5. 使用棋盘格及自选风景图像，分别使用SIFT、FAST及ORB算子检测角点，并比较分析检测结果。 \n",
    "(可选)使用Harris角点检测算子检测棋盘格，并与上述结果比较。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "这里实现的是视觉特征提取，包括：\n",
    "hough直线特征提取\n",
    "harris角点检测\n",
    "sift特征检测\n",
    "surf特征检测\n",
    "ORB特征检测（这里还包括了匹配点的排序筛选操作）\n",
    "'''\n",
    "\n",
    "import cv2\n",
    "import numpy as np\n",
    "import copy\n",
    "\n",
    "'''\n",
    "------------------------------hough直线特征提取----------------------------------------------------------\n",
    "'''\n",
    "#Hough直线特征提取\n",
    "img_build = cv2.imread(r'C:\\\\XYX_use\\\\AI_course\\\\AI_learn\\\\AI_homework\\\\week2\\\\building.jpg')\n",
    "#cv2.imshow('img_build',img_build)\n",
    "#在进行霍夫变换前首先需要转换成灰度图，因为霍夫变换输入必须是单通道的\n",
    "img_build_gray = cv2.cvtColor(img_build,cv2.COLOR_BGR2GRAY)\n",
    "#然后将灰度图进行边缘检测，霍夫变换需要输入边缘检测的结果，这里用常用的canny检测\n",
    "img_build_gray_edges = cv2.Canny(img_build_gray,50,150,apertureSize = 3)\n",
    "#cv2.imshow('img_build_gray_edges',img_build_gray_edges)\n",
    "\n",
    "#然后开始进行霍夫检测——使用HoughLines方法\n",
    "#使用Houghlines的方法无法精确获得直线的首尾坐标，因此会导致画直线图时整个图片都是直线，如果想要精确画出收尾的连线需要概率霍夫变换\n",
    "img_build_lines = cv2.HoughLines(img_build_gray_edges,1,np.pi/180,150)\n",
    "'''\n",
    "函数返回(r,θ)的一个数组，其中r的单位为像素，θ的单位为弧度，将所有的直线的rθ都列出来\n",
    "第一个参数是输入图像，且必须是二值图像，在进行霍夫变换之前需要采用阈值方法的边缘检测；\n",
    "第二和第三个参数分别是r,θ对应的精度；\n",
    "第四个参数是阈值，判定为直线投票数的最小值；\n",
    "注意，投票数取决于直线上点的个数，因此这个阈值代表了检测到的直线的最短长度。\n",
    "'''\n",
    "#print(img_build_lines)\n",
    "#然后开始绘画直线\n",
    "img_build_copy1 = copy.deepcopy(img_build)\n",
    "for i in range(0,len(img_build_lines)):\n",
    "    rho,theta = img_build_lines[i][0][0],img_build_lines[i][0][1]\n",
    "    a = np.cos(theta)\n",
    "    b = np.sin(theta)\n",
    "    x0 = a*rho  #x0、y0表示当前r、θ在直线上的坐标\n",
    "    y0 = b*rho\n",
    "    x1 = int(x0 + 1000 * (-b))  #这里1000是自己给的值，表示当前x0、y0为中心点沿着直线向前和向后画的长度\n",
    "    y1 = int(y0 + 1000 * (a))\n",
    "    x2 = int(x0 - 1000 * (-b))\n",
    "    y2 = int(y0 - 1000 * (a))\n",
    "    cv2.line(img_build_copy1,(x1,y1),(x2,y2),(0,0,255),2) #画出粗细为2的红色直线\n",
    "#cv2.imshow('img_build_HoughLines',img_build_copy1)\n",
    "\n",
    "#进行霍夫检测——使用HoughLinesP方法\n",
    "'''\n",
    "opencv的HoughLinesP函数是统计概率霍夫线变换函数，该函数能输出检测到的直线的端点(x0,y0,x1,y1) ，\n",
    "其函数原型为：HoughLinesP(image, rho, theta, threshold[, lines[, minLineLength[, maxLineGap]]]) -> lines\n",
    "\n",
    "image参数表示边缘检测的输出图像，该图像为单通道8位二进制图像。\n",
    "\n",
    "rho参数表示参数极径  以像素值为单位的分辨率，这里一般使用 1 像素。\n",
    "\n",
    "theta参数表示参数极角  以弧度为单位的分辨率，这里使用 1度。\n",
    "\n",
    "threshold参数表示检测一条直线所需最少的曲线交点。\n",
    "\n",
    "lines参数表示储存着检测到的直线的参数对xstart ystart xstop ystop的容器，也就是线段两个端点的坐标。\n",
    "\n",
    "minLineLength参数表示能组成一条直线的最少点的数量，点数量不足的直线将被抛弃。\n",
    "\n",
    "maxLineGap参数表示能被认为在一条直线上的亮点的最大距离。\n",
    "\n",
    "'''\n",
    "img_build_lines_P = cv2.HoughLinesP(img_build_gray_edges,1,np.pi/180,150,minLineLength=10,maxLineGap=150)\n",
    "img_build_copy2 = copy.deepcopy(img_build)\n",
    "for j in range(0,len(img_build_lines_P)):\n",
    "    xsta,ysta,xend,yend = img_build_lines_P[j,0]\n",
    "    cv2.line(img_build_copy2,(xsta,ysta),(xend,yend),(0,0,255),2)\n",
    "#cv2.imshow('img_build_HoughLinesP',img_build_copy2)\n",
    "\n",
    "'''\n",
    "---------------------harris角点检测--实现1----这里是简单实现的示例，详细实现方法见下面的实现2-----------------\n",
    "'''\n",
    "img_chess = cv2.imread(r'C:\\\\XYX_use\\\\AI_course\\\\AI_learn\\\\AI_homework\\\\week2\\\\buildings.jpg')\n",
    "img_chess_gray = cv2.cvtColor(img_chess,cv2.COLOR_BGR2GRAY) #Harris检测函数输入必须是单通道\n",
    "img_chess_gray_fl = np.float32(img_chess_gray)  #Harris输入图像需要是32位浮点型\n",
    "\n",
    "#Harris角点检测\n",
    "'''\n",
    "cv2.cornerHarris() 可以用来进行角点检测。参数如\n",
    "下:\n",
    "　　• img - 数据类型为 float32 的输入图像。\n",
    "　　• blockSize - 角点检测中要考虑的领域大小。\n",
    "　　• ksize - Sobel 求导中使用的窗口大小\n",
    "　　• k - Harris 角点检测方程中的自由参数,取值参数为 [0.04,0.06],必须在这个范围内.\n",
    "函数的返回值其实就是R值构成的灰度图像　\n",
    "灰度图像坐标会与原图像对应　　\n",
    "Ｒ值就是角点分数　当Ｒ值很大的时候　就可以认为这个点是一个角点\n",
    "'''\n",
    "img_chess_HarrisR = cv2.cornerHarris(img_chess_gray_fl,2,3,0.04)\n",
    "#print(img_chess_HarrisR)\n",
    "img_chess[img_chess_HarrisR>0.01*img_chess_HarrisR.max()] = [0,0,255] #将img_chess图像里所有满足条件的点的像素的BGR的值更改为红色\n",
    "'''\n",
    "img_chess[img_chess_HarrisR>0.01*img_chess_HarrisR.max()]=[0,0,255]这段代码的解释：\n",
    "　    img_chess_HarrisR>0.01*img_chess_HarrisR.max()这么多返回是满足条件的img_chess_HarrisR索引值\n",
    "  　　根据索引值来设置这个点的颜色\n",
    "　　　这里是设定一个阈值　当大于这个阈值分数的都可以判定为角点\n",
    "　　　这里的img_chess_HarrisR其实就是一个个角度分数R组成的---当 λ 1 和 λ 2 都很大，并且 λ 1 与λ 2 近似相等时，R 也很大，（λ 1 和 λ 2 中的最小值都大于阈值）说明这个区域是角点。\n",
    "　　　那么这里为什么要大于０．０１×img_chess_HarrisR.max()呢　注意了这里Ｒ是一个很大的值　我们选取里面最大的Ｒ　\n",
    "     然后　只要img_chess_HarrisR里面的值大于百分之一的Ｒ的最大值　　\n",
    "     那么此时这个img_chess_HarrisR的Ｒ值也是很大的　可以判定他为角点\n",
    "　　 也不一定要０．０１　　　可以根据图像自己选取不过如果太小的话　可能会多圈出几个不同的角点\n",
    "'''\n",
    "#cv2.imshow('chess_harris',img_chess)\n",
    "\n",
    "'''\n",
    "-----------------------------harris角点检测--实现2------------真正的harris角点检测过程-----------------------------\n",
    "'''\n",
    "img_init = cv2.imread(r'C:\\\\XYX_use\\\\AI_course\\\\AI_learn\\\\AI_homework\\\\week2\\\\chessboard.png')\n",
    "img_init_gray = cv2.cvtColor(img_init,cv2.COLOR_BGR2GRAY) #Harris检测函数输入必须是单通道\n",
    "img_init_gray_fl = np.float32(img_init_gray)  #Harris输入图像需要是32位浮点型\n",
    "img_CornerStrength = cv2.cornerHarris(img_init_gray_fl,2,3,0.04)  #通过harris角点检测函数获取角点检测强度的返回值\n",
    "minVal,maxStrength,minIndx,maxIndx = cv2.minMaxLoc(img_CornerStrength) #在角点检测强度结果中，找到最大和最小的阈值\n",
    "#进行局部极大值处理，防止出现角点聚集的情况出现\n",
    "#首先对角点检测后的强度图像进行膨胀滤波处理\n",
    "kernel_1 = cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))  #定义一个3*3的矩阵作为结构矩阵\n",
    "dilated_img = cv2.dilate(img_CornerStrength,kernel_1)\n",
    "#局部最大值检测\n",
    "localMax = cv2.compare(img_CornerStrength,dilated_img,cv2.CMP_EQ)\n",
    "\n",
    "#将图像进一步处理，再一次消除角点数量，得到正确的角点检测结果\n",
    "quilitylevel = 0.03 #阈值提取时的参数，一般在0.03~0.05之间\n",
    "img_threshold = quilitylevel*maxStrength\n",
    "_,cornerTh = cv2.threshold(img_CornerStrength,img_threshold,255,cv2.THRESH_BINARY)\n",
    "cornerTh = np.array(cornerTh)\n",
    "cornerMap = cornerTh.astype(np.uint8)\n",
    "result = cornerTh.astype(np.uint8)\n",
    "cv2.bitwise_and(cornerMap,localMax,cornerMap)  #前两个参数按位与，结果赋给第三个参数\n",
    "cv2.bitwise_and(result,cornerMap,result)\n",
    "#cv2.imshow('result',result)\n",
    "\n",
    "#下面开始找到每个角点的坐标值\n",
    "resultx,resulty = result.shape[0:2]\n",
    "points = []\n",
    "for m in range(resultx):\n",
    "    for n in range(resulty):\n",
    "        if(result[m,n]):\n",
    "            temp = [n,m]\n",
    "            '''注意这里，需要把行列倒一下，才能正确获取结果，这里很容易出错，这是因为：\n",
    "            shape返回的值是图像的行数、列数，但是行数对应的值其实是纵坐标，列数对应的值是横坐标，\n",
    "            因此x行y列其实对应在图像坐标系中是[y,x]点位置。\n",
    "            '''\n",
    "            points.append(temp)\n",
    "#print(points)\n",
    "#print(len(points))\n",
    "for i in points:\n",
    "    i = tuple(i)\n",
    "    #print(i)\n",
    "    cv2.circle(img_init,i,3,(0,0,255),2)\n",
    "#cv2.imshow('img_harris',img_init)\n",
    "\n",
    "\n",
    "'''\n",
    "---------------------sift、surf特征检测与匹配-------------------------------------------------------\n",
    "\n",
    "'''\n",
    "\n",
    "img_1 = cv2.imread(r'C:\\\\XYX_use\\\\AI_course\\\\AI_learn\\\\AI_homework\\\\week2\\\\left.jpg')\n",
    "img_2 = cv2.imread(r'C:\\\\XYX_use\\\\AI_course\\\\AI_learn\\\\AI_homework\\\\week2\\\\right.jpg')\n",
    "\n",
    "minHession = 1000\n",
    "#detector = cv2.xfeatures2d.SIFT_create(minHession)\n",
    "#descriptor = cv2.xfeatures2d.SIFT_create()\n",
    "detector = cv2.xfeatures2d.SURF_create(minHession)\n",
    "descriptor = cv2.xfeatures2d.SURF_create()\n",
    "matcher1 = cv2.DescriptorMatcher_create('BruteForce')\n",
    "#计算特征点\n",
    "KeyPoint1 = detector.detect(img_1)\n",
    "KeyPoint2 = detector.detect(img_2)\n",
    "#计算特征点对应描述子\n",
    "_,descriptors1 = descriptor.compute(img_1,KeyPoint1)\n",
    "_,descriptors2 = descriptor.compute(img_2,KeyPoint2)\n",
    "#描述子匹配\n",
    "matches = matcher1.match(descriptors1,descriptors2)\n",
    "img_matches = np.empty(img_2.shape)  #两个匹配的图的较为完全图\n",
    "img_matches1 = cv2.drawMatches(img_1,KeyPoint1,img_2,KeyPoint2,matches,img_matches)\n",
    "#cv2.imshow('img_matches_sift',img_matches1)\n",
    "cv2.imshow('img_matches_SURF',img_matches1)\n",
    "'''\n",
    "kp,des = img_lena_sift.detectAndCompute(img_lena_gray,None) #找到并计算关键点向量描述\n",
    "img_lena = cv2.drawKeypoints(img_lena,kp,img_lena)#绘制关键点\n",
    "#cv2.imshow('buildings_sift',img_lena)\n",
    "'''\n",
    "\n",
    "'''\n",
    "-----------------------ORB特征检测与匹配（包括匹配点前多少最匹配点的筛选，这个筛选语句通用在各个 特征检测中）------------\n",
    "'''\n",
    "img1 = cv2.imread(r'C:\\\\XYX_use\\\\AI_course\\\\AI_learn\\\\AI_homework\\\\week2\\\\left.jpg', cv2.IMREAD_GRAYSCALE)\n",
    "img2 = cv2.imread(r'C:\\\\XYX_use\\\\AI_course\\\\AI_learn\\\\AI_homework\\\\week2\\\\right.jpg', cv2.IMREAD_GRAYSCALE)\n",
    "orb = cv2.ORB_create()\n",
    "kp1, des1 = orb.detectAndCompute(img1, None)\n",
    "kp2, des2 = orb.detectAndCompute(img2, None)\n",
    "bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)\n",
    "matches = bf.match(des1, des2)\n",
    "#matches = sorted(matches, key=lambda x: x.distance)\n",
    "#img3 = cv2.drawMatches(img1, kp1, img2, kp2, matches[:80], img2, flags=2)\n",
    "#上面这两句被注释掉的语句，作用是将匹配的结果进行筛选，将匹配结果进行排序，然后选取前80组最匹配的特征进行连线匹配\n",
    "img3 = cv2.drawMatches(img1, kp1, img2, kp2, matches, img2, flags=2)\n",
    "cv2.imshow('img_matches_ORB',img3)\n",
    "\n",
    "cv2.waitKey()\n",
    "cv2.destroyAllWindows()\n",
    "\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
